// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.IO;
using System.Collections.Generic;
using System.Reflection;
using CIP4.Common;

namespace CIP4.Serializer
{
    public class XsdBuilderType : SerializerBuilderBaseType
    {
        private readonly TypeContextType _type;

        private readonly GeneratorTemplateType _context;

        private bool _writtenHeader;

        private readonly Dictionary<string, TypeContextType> _doneThese = new Dictionary<string, TypeContextType>();

        private readonly Dictionary<string, TypeContextType> _doThese = new Dictionary<string, TypeContextType>();

        public string Xsd
        {
            get { return _context.Writer.ToString(); }
        }

        public XsdBuilderType(TypeContextType aType)
        {
            _type = aType;
            _context = new GeneratorTemplateType("XsdTemplates");
        }

        public string GetXsd(Dictionary<string, TypeContextType> alreadyDone)
        {
            try
            {
                if (alreadyDone.ContainsKey(_type.TheTypeFullName) || _type.TheTypeName.EndsWith("<>"))
                    return string.Empty;
                Globals.AddAssemblyPath(Path.GetDirectoryName(_type.TheType.Assembly.Location), Globals.DefaultLogger);
                return XsdHeader();
            }
            finally
            {
                Globals.RemoveAssemblyPath(Path.GetDirectoryName(_type.TheType.Assembly.Location));
            }
        }

        private string XsdHeader()
        {
            PublishHeader(_type.TheTypeFullName);
            if (_doneThese.ContainsKey(_type.TheTypeFullName))
                return Xsd;
            _context.Writer.WriteLine(_context["TypeXsdStart"].
                Replace("<typeString>", _type.TheTypeName).
                Replace("<typeName>", _type.TheTypeName).
                Replace("<qualifiedTypeName>", _type.TheTypeFullName));
            PublishCompound(_type);
            PublishTrailer();
            return Xsd;
        }

        private void PublishHeader(string path)
        {
            if (!_writtenHeader)
            {
                _context.Writer.WriteLine(_context["ItemXsdStart"].
                    Replace("<schemaName>", path).
                    Replace("<date>", DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToShortTimeString()).
                    Replace("<companyName>", "CIP4.org").
                    Replace("<namespace>", Globals.ciiNS));
                _writtenHeader = true;
            }
        }

        public void PublishTrailer()
        {
            DoUsedTypes();
            _context.Writer.WriteLine(_context["XsdEnd"]);
        }

        public void PublishCompound(TypeContextType type)
        {
            if (_doneThese.ContainsKey(type.TheTypeFullName))
                return;
            _doneThese.Add(type.TheTypeFullName, type);
            _context.Writer.WriteLine(_context["XsdForCompound"].
                Replace("<outerElement>", XsdForOuterElement(type)).
                Replace("<className>", type.TheTypeFullName).
                Replace("<classProperties>", XsdForTypeProperties(type)));
            _context.Writer.WriteLine(_context["XsdForCompoundProperties"].
                Replace("<className>", type.TheTypeFullName).
                Replace("<classProperties>", XsdForProperties(type)).
                Replace("<facets>", ""));
        }

        private string XsdForProperties(TypeContextType type)
        {
            TextWriter writer = new StringWriter();
            foreach (PropertyInfo property in WrapperSerializerType.SortedProperties(type.TheType.GetProperties()).Values)
                if (IncludeThisProperty(property))
                    XsdForProperty(property, writer);
            return writer.ToString();
        }

        private readonly HashSet<string> _dontDoThese = new HashSet<string> {"System.IO.Stream"};

        private void AddTypeTree(TypeContextType type, Dictionary<string, TypeContextType> doThese)
        {
            if (doThese.ContainsKey(type.TheTypeFullName) || _dontDoThese.Contains(type.TheTypeFullName))
                return;
            doThese.Add(type.TheTypeFullName, type);
            foreach (Type subType in Subtypes(type.TheType))
                AddTypeTree(new TypeContextType(subType), doThese);
        }

        private static IEnumerable<Type> Subtypes(Type aType)
        {
            foreach (Type t in aType.Assembly.GetTypes())
            {
                if (!aType.IsAssignableFrom(t) || t == aType)
                    continue;
                yield return t;
            }
            yield break;
        }

        private static bool IsSimpleType(PropertyInfo prop)
        {
            return ValueType(prop.PropertyType);
        }


        private void XsdForProperty(PropertyInfo prop, TextWriter writer)
        {
            if (!_doThese.ContainsKey(new TypeContextType(prop.PropertyType).TheTypeFullName))
                AddTypeTree(new TypeContextType(prop.PropertyType), _doThese);
            if (IsSimpleType(prop))
            {
                if (prop.PropertyType.GetInterface("IList") != null && prop.PropertyType.IsEnum)
                    writer.WriteLine(_context["XsdSimpleMVEnumProperty"].
                        Replace("<propertyName>", prop.Name).
                        Replace("<maxOccurs>", "unbounded").
                        Replace("<instanceName>", new TypeContextType(prop.PropertyType).TheTypeName).
                        Replace("<typeName>", new TypeContextType(prop.PropertyType).TheTypeFullName));
                else if (prop.PropertyType.GetInterface("IList") != null)
                    writer.WriteLine(_context["XsdSimpleMVProperty"].
                        Replace("<propertyName>", prop.Name).
                        Replace("<maxOccurs>", "unbounded").
                        Replace("<instanceName>", new TypeContextType(prop.PropertyType).TheTypeName).
                        Replace("<typeName>", new TypeContextType(prop.PropertyType).TheTypeFullName));
                else
                    writer.WriteLine(_context["XsdSimpleSVProperty"].
                        Replace("<propertyName>", prop.Name).
                        Replace("<typeName>", XsdSuitableType(prop.PropertyType).FullName).
                        Replace("<minOccurs>", "0"));
            }
            else
            {
                if (prop.PropertyType.GetInterface("IList") != null)
                    writer.WriteLine(_context["XsdCompoundMVProperty"].
                        Replace("<propertyName>", prop.Name).
                        Replace("<maxOccurs>", "unbounded").
                        Replace("<classproperties>", XsdForTypeProperties(new TypeContextType(prop.PropertyType))));
                else
                    writer.WriteLine(_context["XsdCompoundSVProperty"].
                        Replace("<propertyName>", prop.Name).
                        Replace("<typeName>", XsdSuitableType(prop.PropertyType).FullName).
                        Replace("<minOccurs>", "0"));
            }
        }

        private static Type XsdSuitableType(Type aType)
        {
            if (aType == typeof(Stream))
                return typeof(string);
            return aType;
        }

        private string XsdForTypeProperties(TypeContextType type)
        {
            string text = _context["XsdTypeProperty"].
                Replace("<propertyName>", type.TheType.Name).
                Replace("<typeName>", type.TheTypeFullName);
            foreach (Type sub in Subtypes(type.TheType))
                text +=
                    _context["XsdTypeProperty"].
                        Replace("<propertyName>", sub.Name).
                        Replace("<typeName>", new TypeContextType(sub).TheTypeFullName);
            return text;
        }

        private string XsdForOuterElement(TypeContextType type)
        {
            string text = _context["XsdForOuterElement"].
                Replace("<propertyName>", type.TheType.Name).
                Replace("<typeName>", type.TheTypeFullName);
            return text;
        }

        private void PublishSimple(TypeContextType type)
        {
            if (_doneThese.ContainsKey(type.TheTypeFullName))
                return;
            _doneThese.Add(type.TheTypeFullName, type);
            XsdForSimple(type);
        }

        public void XsdForSimple(TypeContextType type)
        {
            if (type.TheType.IsEnum)
            {
                _context.Writer.WriteLine(_context["XsdEnumType"].
                    Replace("<enumName>", type.TheTypeFullName).
                    Replace("<instanceName>", type.TheType.Name).
                    Replace("<baseType>", "xs:string").
                    Replace("<enums>", EnumList(type)).
                    Replace("<facets>", ""));
            }
            else if (type.TheType == typeof(Guid))
            {
                _context.Writer.WriteLine(_context["xsdSimpleGuidProperty"].
                    Replace("<typeName>", type.TheTypeFullName).
                    Replace("<instanceName>", type.TheType.Name).
                    Replace("<facets>", ""));
            }
            else
            {
                _context.Writer.WriteLine(_context["XsdSimpleType"].
                    Replace("<typeName>", type.TheTypeFullName).
                    Replace("<instanceName>", type.TheTypeName).
                    Replace("<underlyingTypeName>", XsdUnderlyingTypeName(type)).
                    Replace("<facets>", ""));
            }
        }

        private string EnumList(TypeContextType type)
        {
            StringWriter wrtr = new StringWriter();
            foreach (FieldInfo f in type.TheType.GetFields())
            {
                if (f.IsLiteral)
                    wrtr.WriteLine(_context["XsdEnumValueId"].
                        Replace("<value>", f.GetValue(new object()).ToString()).
                        Replace("<id>", ((int)f.GetValue(new object())).ToString()).
                        Replace("<documentation>", ""));
            }
            return wrtr.ToString();
        }

        public string XsdUnderlyingTypeName(TypeContextType type)
        {
            return XsdTypeForPrimitiveType(type);
        }

        public string XsdTypeForPrimitiveType(TypeContextType type)
        {
            if (type.TheType == typeof(bool))
                return "xs:boolean";
            if (type.TheType == typeof(DateTime))
                return "xs:dateTime";
            if (type.TheType == typeof(decimal))
                return "xs:decimal";
            if (type.TheType == typeof(float) || type.TheType == typeof(double))
                return "xs:double";
            if (type.TheType == typeof(int) || type.TheType == typeof(long) || type.TheType == typeof(uint) || type.TheType == typeof(ulong))
                return "xs:long";
            if (type.TheType == typeof(Guid))
                return "xs:string";
            if (type.TheType == typeof(string))
                return "xs:string";
            throw new InvalidOperationException(string.Format("{0} is not a recognized type", type));
        }

        private void DoUsedTypes()
        {
            do
            {
                List<TypeContextType> doTheseNext = new List<TypeContextType>();
                doTheseNext.AddRange(_doThese.Values);
                _doThese.Clear();
                foreach (TypeContextType type in doTheseNext)
                {
                    if (ValueType(type.TheType))
                        PublishSimple(type);
                    else
                        PublishCompound(type);
                }
            } while (_doThese.Count > 0);
        }
    }
}
